home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / Dialing Addresses / Src / AddressDB.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  55.4 KB  |  1,891 lines

  1. /*******************************************************************
  2.  Copyright © 1995 - 1998, 3Com Corporation or its subsidiaries ("3Com").  
  3.  All rights reserved.
  4.    
  5.  This software may be copied and used solely for developing products for 
  6.  the Palm Computing platform and for archival and backup purposes.  Except 
  7.  for the foregoing, no part of this software may be reproduced or transmitted 
  8.  in any form or by any means or used to make any derivative work (such as 
  9.  translation, transformation or adaptation) without express written consent 
  10.  from 3Com.
  11.  
  12.  3Com reserves the right to revise this software and to make changes in content 
  13.  from time to time without obligation on the part of 3Com to provide notification 
  14.  of such revision or changes.  
  15.  3COM MAKES NO REPRESENTATIONS OR WARRANTIES THAT THE SOFTWARE IS FREE OF ERRORS 
  16.  OR THAT THE SOFTWARE IS SUITABLE FOR YOUR USE.  THE SOFTWARE IS PROVIDED ON AN 
  17.  "AS IS" BASIS.  3COM MAKES NO WARRANTIES, TERMS OR CONDITIONS, EXPRESS OR IMPLIED, 
  18.  EITHER IN FACT OR BY OPERATION OF LAW, STATUTORY OR OTHERWISE, INCLUDING WARRANTIES, 
  19.  TERMS, OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 
  20.  SATISFACTORY QUALITY.
  21.  
  22.  TO THE FULL EXTENT ALLOWED BY LAW, 3COM ALSO EXCLUDES FOR ITSELF AND ITS SUPPLIERS 
  23.  ANY LIABILITY, WHETHER BASED IN CONTRACT OR TORT (INCLUDING NEGLIGENCE), FOR 
  24.  DIRECT, INCIDENTAL, CONSEQUENTIAL, INDIRECT, SPECIAL, OR PUNITIVE DAMAGES OF 
  25.  ANY KIND, OR FOR LOSS OF REVENUE OR PROFITS, LOSS OF BUSINESS, LOSS OF INFORMATION 
  26.  OR DATA, OR OTHER FINANCIAL LOSS ARISING OUT OF OR IN CONNECTION WITH THIS SOFTWARE, 
  27.  EVEN IF 3COM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
  28.  
  29.  3Com, HotSync, Palm Computing, and Graffiti are registered trademarks, and 
  30.  Palm III and Palm OS are trademarks of 3Com Corporation or its subsidiaries.
  31.  
  32.  IF THIS SOFTWARE IS PROVIDED ON A COMPACT DISK, THE OTHER SOFTWARE AND 
  33.  DOCUMENTATION ON THE COMPACT DISK ARE SUBJECT TO THE LICENSE AGREEMENT 
  34.  ACCOMPANYING THE COMPACT DISK.
  35.  
  36.  *-------------------------------------------------------------------
  37.  * FileName:
  38.  *      AddressMgr.c
  39.  *
  40.  * Description:
  41.  *      Address Manager routines
  42.  *
  43.  * History:
  44.  *      1/9/95  rsf - Created
  45.  *      7/22/96  rsf - Added the Address Lookup routines.  These calls
  46.  *                     perform the data searching needed by the app to
  47.  *                     support the lookup launch notification.
  48.  *
  49.  *******************************************************************/
  50.  
  51.  
  52. // Set this to get to private database defines
  53. #define __ADDRMGR_PRIVATE__
  54.  
  55. #include <Pilot.h>
  56. #include "CharAttr.h"
  57. #include "AddressDB.h"
  58.  
  59.  
  60. // Extract the bit at position index from bitfield.  0 is the high bit.
  61. #define BitAtPosition(pos)                  ((ULong)1 << (pos))
  62. #define GetBitMacro(bitfield, index)      ((bitfield) & BitAtPosition(index))
  63. #define SetBitMacro(bitfield, index)      ((bitfield) |= BitAtPosition(index))
  64. #define RemoveBitMacro(bitfield, index)   ((bitfield) &= ~BitAtPosition(index))
  65.  
  66.  
  67.  
  68. #define sortKeyFieldBits   (BitAtPosition(name) | \
  69.                             BitAtPosition(firstName) | \
  70.                             BitAtPosition(company))
  71. //0x70000         // Update this if the sort fields 
  72.                                  // change positions
  73.  
  74. // The following structure doesn't really exist.  The first field
  75. // varies depending on the data present.  However, it is convient
  76. // (and less error prone) to use when accessing the other information.
  77. typedef struct {
  78.       AddrOptionsType      options;        // Display by company or by name
  79.       AddrDBRecordFlags      flags;
  80.       unsigned char         companyFieldOffset;   // Offset from firstField
  81.       char                  firstField;
  82. } AddrPackedDBRecord;
  83.  
  84.  
  85. /************************************************************
  86.  * Private routines used only in this module
  87.  *************************************************************/
  88.  
  89.  
  90.  
  91. /************************************************************
  92.  *
  93.  *  FUNCTION: AddrAppInfoGetPtr
  94.  *
  95.  *  DESCRIPTION: Return a locked pointer to the AddrAppInfo or NULL
  96.  *
  97.  *  PARAMETERS: dbP - open database pointer
  98.  *
  99.  *  RETURNS: locked ptr to the AddrAppInfo or NULL
  100.  *
  101.  *  CREATED: 6/13/95 
  102.  *
  103.  *  BY: Roger Flores
  104.  *
  105.  *************************************************************/
  106. AddrAppInfoPtr   AddrAppInfoGetPtr(DmOpenRef dbP)
  107. {
  108.    UInt       cardNo;
  109.    LocalID    dbID;
  110.    LocalID    appInfoID;
  111.    
  112.    if (DmOpenDatabaseInfo(dbP, &dbID, NULL, NULL, &cardNo, NULL))
  113.       return NULL;
  114.    if (DmDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &appInfoID, NULL, NULL, NULL))
  115.       return NULL;
  116.  
  117.    if (appInfoID == NULL)
  118.       return NULL;
  119.    else
  120.       return MemLocalIDToLockedPtr(appInfoID, cardNo);
  121.    
  122. }   
  123.  
  124.  
  125. /************************************************************
  126.  *
  127.  *  FUNCTION: AddrChangeCountry
  128.  *
  129.  *  DESCRIPTION: Set the field labels to those appropriate
  130.  *  to the current country (based on system preferences).
  131.  *
  132.  *  PARAMETERS: application info ptr
  133.  *
  134.  *  RETURNS: nothing
  135.  *
  136.  *  CREATED: 7/24/95 
  137.  *
  138.  *  BY: Roger Flores
  139.  *
  140.  *************************************************************/
  141. void AddrChangeCountry(AddrAppInfoPtr appInfoP)
  142. {
  143.    CountryType countryCurrent;
  144.    AddrAppInfoPtr   nilP = 0;
  145.    AddrDBRecordFlags   dirtyFieldLabels;
  146.    const Char * cityP = NULL;
  147.    const Char * stateP = NULL;
  148.    const Char * zipCodeP = NULL;
  149.  
  150.  
  151.    // Localize the field labels to the current country
  152.    countryCurrent = (CountryType) PrefGetPreference(prefCountry);
  153.    
  154.    
  155. #if COUNTRY == COUNTRY_UNITED_STATES
  156.    switch (countryCurrent)
  157.       {
  158.       case cUnitedStates:
  159.          cityP = "City";
  160.          stateP = "State";
  161.          zipCodeP = "Zip Code";
  162.          break;
  163.       
  164.       case cAustralia:
  165.          cityP = "City";
  166.          stateP = "State";
  167.          zipCodeP = "Post Code";
  168.          break;
  169.       
  170.       case cCanada:
  171.          cityP = "City";
  172.          stateP = "Province";
  173.          zipCodeP = "Postal Code";
  174.          break;
  175.       
  176.       case cUnitedKingdom:
  177.          cityP = "Town";
  178.          stateP = "County";
  179.          zipCodeP = "Post Code";
  180.          break;
  181.       
  182.       default:
  183.           break;
  184.       }
  185. #endif
  186.    
  187.    
  188.    // If labels have changed write them out.
  189.    if (cityP != NULL)
  190.       {
  191.       DmStrCopy(appInfoP, (ULong) nilP->fieldLabels[city], cityP);
  192.       DmStrCopy(appInfoP, (ULong) nilP->fieldLabels[state], stateP);
  193.       DmStrCopy(appInfoP, (ULong) nilP->fieldLabels[zipCode], zipCodeP);
  194.       dirtyFieldLabels.allBits = (appInfoP->dirtyFieldLabels.allBits) | 
  195.          BitAtPosition(city) | BitAtPosition(state) | BitAtPosition(zipCode);
  196.       DmWrite(appInfoP, (ULong) &nilP->dirtyFieldLabels, &dirtyFieldLabels, sizeof dirtyFieldLabels);
  197.       }
  198.    
  199.    
  200.    // Record the country.
  201.    DmWrite(appInfoP, (ULong) &nilP->country, &countryCurrent, sizeof(countryCurrent));
  202. }
  203.  
  204.  
  205. /************************************************************
  206.  *
  207.  *  FUNCTION: AddrLocalizeAppInfo
  208.  *
  209.  *  DESCRIPTION: Look for localize app info strings and copy
  210.  *  them into the app info block.
  211.  *
  212.  *  PARAMETERS: application info ptr
  213.  *
  214.  *  RETURNS: nothing
  215.  *
  216.  *  CREATED: 12/13/95 
  217.  *
  218.  *  BY: Roger Flores
  219.  *
  220.  *  MODIFICATIONS:
  221.  *      10/22/96   roger      Set flags when field modified 
  222.  *************************************************************/
  223. static void AddrLocalizeAppInfo(AddrAppInfoPtr appInfoP)
  224. {
  225.    VoidHand       localizedAppInfoH;
  226.    CharPtr          localizedAppInfoP;
  227.    AddrAppInfoPtr   nilP = 0;
  228.    VoidHand       stringsH;
  229.    CharPtr         *stringsP;
  230.    int             i;
  231.    UInt            localRenamedCategories;
  232.    ULong            localDirtyFieldLabels;
  233.  
  234.  
  235.    localizedAppInfoH = DmGetResource(appInfoStringsRsc, LocalizedAppInfoStr);
  236.    if (!localizedAppInfoH)
  237.       return;
  238.    localizedAppInfoP = MemHandleLock(localizedAppInfoH);
  239.    stringsH = SysFormPointerArrayToStrings(localizedAppInfoP, 
  240.       dmRecNumCategories + addrNumFields + numPhoneLabelsStoredSecond);
  241.    stringsP = MemHandleLock(stringsH);
  242.    
  243.    
  244.    // Copy each category
  245.    localRenamedCategories = appInfoP->renamedCategories;
  246.    for (i = 0; i < dmRecNumCategories; i++)
  247.       {
  248.       if (stringsP[i][0] != '\0')
  249.          {
  250.          DmStrCopy(appInfoP, (ULong) nilP->categoryLabels[i], stringsP[i]);
  251.          SetBitMacro(localRenamedCategories, i);
  252.          }
  253.       }
  254.    DmWrite(appInfoP, (ULong) &nilP->renamedCategories, &localRenamedCategories,  
  255.       sizeof(localRenamedCategories));
  256.    
  257.    
  258.    // Copy each field label
  259.    localDirtyFieldLabels = appInfoP->dirtyFieldLabels.allBits;
  260.    for (i = 0; i < (addrNumFields + numPhoneLabelsStoredSecond); i++)
  261.       {
  262.       if (stringsP[i + dmRecNumCategories][0] != '\0')
  263.          {
  264.          DmStrCopy(appInfoP, (ULong) nilP->fieldLabels[i], 
  265.             stringsP[i + dmRecNumCategories]);
  266.          SetBitMacro(localDirtyFieldLabels, i);
  267.          }
  268.       }
  269.    DmWrite(appInfoP, (ULong) &nilP->dirtyFieldLabels.allBits, &localDirtyFieldLabels,  
  270.       sizeof(localDirtyFieldLabels));
  271.    
  272.    
  273.    MemPtrFree(stringsP);
  274.    MemPtrUnlock(localizedAppInfoP);
  275.    DmReleaseResource(localizedAppInfoH);
  276. }
  277.  
  278.  
  279. /************************************************************
  280.  *
  281.  *  FUNCTION: AddrAppInfoInit
  282.  *
  283.  *  DESCRIPTION: Create an app info chunk if missing.  Set
  284.  *      the strings to a default.
  285.  *
  286.  *  PARAMETERS: dbP - open database pointer
  287.  *
  288.  *  RETURNS: 0 if successful, errorcode if not
  289.  *
  290.  *  CREATED: 1/3/95 
  291.  *
  292.  *  BY: Roger Flores
  293.  *
  294.  *  MODIFICATIONS:
  295.  *      10/22/96   roger      Change to init data via code and resources to 
  296.  *                        remove global var use which wasn't always available.
  297.  *************************************************************/
  298. Err   AddrAppInfoInit(DmOpenRef dbP)
  299. {
  300.    UInt            cardNo;
  301.    LocalID          dbID;
  302.    LocalID          appInfoID;
  303.    Handle         h;
  304.    AddrAppInfoPtr   appInfoP;
  305.    AddrAppInfoPtr defaultAddrApplicationInfoP;
  306.    int            i;
  307.    
  308.    
  309.    appInfoP = AddrAppInfoGetPtr(dbP);
  310.  
  311.    // If there isn't an AddrApplicationInfo make space for one
  312.    if (appInfoP == NULL)
  313.       {
  314.       if (DmOpenDatabaseInfo(dbP, &dbID, NULL, NULL, &cardNo, NULL))
  315.          return dmErrInvalidParam;
  316.       if (DmDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &appInfoID, NULL, NULL, NULL))
  317.          return dmErrInvalidParam;
  318.  
  319.       h = DmNewHandle(dbP, sizeof(AddrAppInfoType));
  320.       if (!h) return dmErrMemError;
  321.       
  322.       appInfoID = MemHandleToLocalID( h);
  323.       DmSetDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &appInfoID, NULL, NULL, NULL);
  324.  
  325.       appInfoP = MemHandleLock(h);
  326.       }
  327.    
  328.    
  329.    // Allocate & Clear the app info
  330.    defaultAddrApplicationInfoP = MemPtrNew(sizeof(AddrAppInfoType));
  331.    if (defaultAddrApplicationInfoP == NULL)
  332.       {
  333.       ErrDisplay("Unable to init AddressDB");
  334.       return 1;
  335.       }
  336.    
  337.    MemSet(defaultAddrApplicationInfoP, sizeof(AddrAppInfoType), 0);
  338.    
  339.    // Init the categories
  340.    for (i = 0; i < dmRecNumCategories; i++)
  341.       {
  342.       defaultAddrApplicationInfoP->categoryUniqIDs[i] = i;
  343.       }
  344.    defaultAddrApplicationInfoP->lastUniqID = dmRecNumCategories - 1;
  345.    
  346.    // Set to sort by name
  347.    defaultAddrApplicationInfoP->misc.sortByCompany = false;
  348.    
  349.  
  350.    // copy in the defaults and free the default app info
  351.    DmWrite(appInfoP, 0, defaultAddrApplicationInfoP,  sizeof(AddrAppInfoType));
  352.    MemPtrFree(defaultAddrApplicationInfoP);
  353.    
  354.    
  355.    // Try to use localized app info block strings.
  356.    AddrLocalizeAppInfo(appInfoP);
  357.  
  358.    // Localize the field labels to the current country
  359.    AddrChangeCountry(appInfoP);
  360.       
  361.    
  362.    // Unlock
  363.    MemPtrUnlock(appInfoP);
  364.    
  365.    return 0;
  366. }
  367.  
  368.  
  369. /************************************************************
  370.  *
  371.  *  FUNCTION: AddrSetFieldLabel
  372.  *
  373.  *  DESCRIPTION: Set a field's label and mark it dirty.
  374.  *
  375.  *  PARAMETERS: dbP - open database pointer
  376.  *                fieldNum - field label to change
  377.  *                fieldLabel - new field label to use
  378.  *
  379.  *  RETURNS: 0 if successful, errorcode if not
  380.  *
  381.  *  CREATED: 6/28/95 
  382.  *
  383.  *  BY: Roger Flores
  384.  *
  385.  *************************************************************/
  386. void AddrSetFieldLabel(DmOpenRef dbP, UInt fieldNum, CharPtr fieldLabel)
  387. {
  388.    AddrAppInfoPtr    appInfoP;
  389.    AddrAppInfoType   copy;
  390.  
  391.  
  392.    ErrFatalDisplayIf(fieldNum >= lastLabel, 
  393.       "fieldNum out of range");   
  394.    
  395.    // Get a copy of the app info
  396.    appInfoP = AddrAppInfoGetPtr(dbP);
  397.    ErrFatalDisplayIf(appInfoP == NULL, 
  398.       "Bad database (invalid or no app info block)");
  399.    MemMove(©, appInfoP, sizeof(copy));
  400.    
  401.    // Make the changes
  402.    StrCopy(copy.fieldLabels[fieldNum], fieldLabel); //lint !e661 
  403.    SetBitMacro(copy.dirtyFieldLabels.allBits, fieldNum);
  404.    
  405.    // Write changes to record
  406.    DmWrite(appInfoP, 0, ©, sizeof(copy));
  407.    
  408.    // Unlock app info
  409.    MemPtrUnlock(appInfoP);
  410. }
  411.    
  412.  
  413.  
  414. /************************************************************
  415.  *
  416.  *  FUNCTION: AddrFindKey
  417.  *
  418.  *  DESCRIPTION: Return the next valid key
  419.  *
  420.  *  PARAMETERS: database packed record
  421.  *            <-> key to use (ptr to string or NULL for uniq ID)
  422.  *            <-> which key (incremented for use again, starts at 1)
  423.  *            -> sortByCompany
  424.  *
  425.  *  RETURNS: 
  426.  *
  427.  *  CREATED: 1/16/95 
  428.  *
  429.  *  BY: Roger Flores
  430.  *
  431.  *   COMMENTS:   Returns the key which is asked for if possible and 
  432.  *   advances whichKey.  If the key is not available the key advances
  433.  *   to the next one.  The order of keys is:
  434.  *
  435.  * if sortByCompany:
  436.  *      companyKey, nameKey, firstNameKey, uniq ID
  437.  *
  438.  * if !sortByCompany:
  439.  *      nameKey, firstNameKey, companyKey (if no name or first name), uniq ID
  440.  *
  441.  *
  442.  *************************************************************/
  443.  
  444. static void AddrFindKey(AddrPackedDBRecord *r, char **key, UInt *whichKey, 
  445.    Int sortByCompany)
  446. {
  447.    AddrDBRecordFlags fieldFlags;
  448.    
  449.    fieldFlags.allBits = r->flags.allBits;
  450.  
  451.    ErrFatalDisplayIf(*whichKey == 0 || *whichKey == 5, "Bad addr key");
  452.  
  453.    if (sortByCompany)
  454.       {
  455.       if (*whichKey == 1 && fieldFlags.bits.company)
  456.          {
  457.          *whichKey = 2;
  458.          goto returnCompanyKey;
  459.          }
  460.       
  461.       if (*whichKey <= 2 && fieldFlags.bits.name)
  462.          {
  463.          *whichKey = 3;
  464.          goto returnNameKey;
  465.          }
  466.       
  467.       if (*whichKey <= 3 && fieldFlags.bits.firstName)
  468.          {
  469.          *whichKey = 4;
  470.          goto returnFirstNameKey;
  471.          }
  472.       }
  473.    else
  474.       {
  475.       if (*whichKey == 1 && fieldFlags.bits.name)
  476.          {
  477.          *whichKey = 2;
  478.          goto returnNameKey;
  479.          }
  480.       
  481.       if (*whichKey <= 2 && fieldFlags.bits.firstName)
  482.          {
  483.          *whichKey = 3;
  484.          goto returnFirstNameKey;
  485.          }
  486.  
  487.       // For now don't consider company name when sorting by person name
  488.       // unless there isn't a name or firstName
  489.       if (*whichKey <= 3 && fieldFlags.bits.company &&
  490.           !(fieldFlags.bits.name || fieldFlags.bits.firstName))
  491.          {
  492.          *whichKey = 4;
  493.          goto returnCompanyKey;
  494.          }
  495.  
  496.       }
  497.    
  498.    // All possible fields have been tried so return NULL so that
  499.    // the uniq ID is compared.
  500.    *whichKey = 5;
  501.    *key = NULL;
  502.    return;
  503.  
  504.  
  505.  
  506. returnCompanyKey:
  507.    *key = (char *) &r->companyFieldOffset + r->companyFieldOffset;
  508.    return;
  509.    
  510.    
  511. returnNameKey:
  512.    *key = &r->firstField;
  513.    return;
  514.    
  515.    
  516. returnFirstNameKey:
  517.    *key = &r->firstField;
  518.    if (r->flags.bits.name)
  519.       {
  520.       *key += StrLen(*key) + 1;
  521.       }
  522.    return;
  523.    
  524. }
  525.  
  526.  
  527. /************************************************************
  528.  *
  529.  *  FUNCTION: AddrComparePackedRecords
  530.  *
  531.  *  DESCRIPTION: Compare two packed records.
  532.  *
  533.  *  PARAMETERS: address record 1
  534.  *            address record 2
  535.  *
  536.  *  RETURNS: -1 if record one is less
  537.  *           1 if record two is less
  538.  *
  539.  *  CREATED: 1/14/95 
  540.  *
  541.  *  BY: Roger Flores
  542.  *
  543.  *   COMMENTS:   Compare the two records key by key until
  544.  *   there is a difference.  Return -1 if r1 is less or 1 if r2
  545.  *   is less.  A zero may be returned if two records
  546.  *   seem identical.
  547.  * NULL fields are considered less than others.
  548.  *
  549.  *************************************************************/
  550.  
  551. static Int AddrComparePackedRecords(AddrPackedDBRecord *r1, AddrPackedDBRecord *r2, 
  552.    Int sortByCompany, SortRecordInfoPtr info1, SortRecordInfoPtr info2, 
  553.    VoidHand appInfoH)
  554. {
  555.    UInt whichKey1, whichKey2;
  556.    char *key1, *key2;
  557.    Int result;
  558.  
  559.    whichKey1 = 1;
  560.    whichKey2 = 1;
  561.    
  562.    do {
  563.       AddrFindKey(r1, &key1, &whichKey1, sortByCompany);
  564.       AddrFindKey(r2, &key2, &whichKey2, sortByCompany);
  565.       
  566.       // A key with NULL loses the StrCompare.
  567.       if (key1 == NULL)
  568.          {
  569.          // If both are NULL then return them as equal
  570.          if (key2 == NULL)
  571.             {
  572.             result = 0;
  573.             return result;
  574.             }
  575.          else
  576.             result = -1;
  577.          }
  578.       else
  579.       if (key2 == NULL)
  580.          result = 1;
  581.       else
  582.          {
  583.          result = StrCaselessCompare(key1, key2);
  584.          if (result == 0)
  585.             result = StrCompare(key1, key2);
  586.          }
  587.  
  588.    } while (!result);
  589.    
  590.    
  591.    return result;
  592. }
  593.  
  594.  
  595. /************************************************************
  596.  *
  597.  *  FUNCTION: AddrUnpackedSize
  598.  *
  599.  *  DESCRIPTION: Return the size of an AddrDBRecordType
  600.  *
  601.  *  PARAMETERS: address record
  602.  *
  603.  *  RETURNS: the size in bytes
  604.  *
  605.  *  CREATED: 1/10/95 
  606.  *
  607.  *  BY: Roger Flores
  608.  *
  609.  *************************************************************/
  610. static UInt AddrUnpackedSize(AddrDBRecordPtr r)
  611. {
  612.    UInt size;
  613.    Int   index;
  614.    
  615.    size = sizeof (AddrPackedDBRecord) - sizeof (char);   // correct
  616.    for (index = firstAddressField; index < addressFieldsCount; index++)
  617.       {
  618.       if (r->fields[index] != NULL)
  619.          size += StrLen(r->fields[index]) + 1;
  620.       }
  621.    return size;
  622. }
  623.  
  624.  
  625. /************************************************************
  626.  *
  627.  *  FUNCTION: AddrPack
  628.  *
  629.  *  DESCRIPTION: Pack an AddrDBRecordType.  Doesn't pack empty strings.
  630.  *
  631.  *  PARAMETERS: address record to pack
  632.  *                address record to pack into
  633.  *
  634.  *  RETURNS: the AddrPackedDBRecord is packed
  635.  *
  636.  *  CREATED: 1/10/95 
  637.  *
  638.  *  BY: Roger Flores
  639.  *
  640.  *************************************************************/
  641. static void AddrPack(AddrDBRecordPtr s, VoidPtr recordP)
  642. {
  643.    ULong                  offset;
  644.    AddrDBRecordFlags    flags;
  645.    Int                  index;
  646.    AddrPackedDBRecord*   d=0;
  647.    UInt                  len;
  648.    VoidPtr               srcP;
  649.    Char                  companyFieldOffset;
  650.    
  651.    flags.allBits = 0;
  652.  
  653.    DmWrite(recordP, (ULong)&d->options, &s->options, sizeof(s->options));
  654.    offset = (ULong)&d->firstField;
  655.    
  656.    for (index = firstAddressField; index < addressFieldsCount; index++) {
  657.       if (s->fields[index] != NULL)
  658. /*         if (s->fields[index][0] == '\0')
  659.             {
  660.             // so set the companyFieldOffset or clear it code doesn't fail
  661.             s->fields[index] = NULL;   
  662.             }
  663.          else
  664. */         {
  665.          ErrFatalDisplayIf(s->fields[index][0] == '\0' && index != note, 
  666.             "Empty field being added");
  667.          srcP = s->fields[index];
  668.          len = StrLen(srcP) + 1;
  669.          DmWrite(recordP, offset, srcP, len);
  670.          offset += len;
  671.          SetBitMacro(flags.allBits, index);
  672.          }
  673.       }
  674.  
  675.    // Set the flags indicating which fields are used   
  676.    DmWrite(recordP, (ULong)&d->flags.allBits, &flags.allBits, sizeof(flags.allBits));
  677.  
  678.    // Set the companyFieldOffset or clear it
  679.    if (s->fields[company] == NULL)
  680.       companyFieldOffset = 0;
  681.    else {
  682.       index = 1;
  683.       if (s->fields[name] != NULL)
  684.          index += StrLen(s->fields[name]) + 1;
  685.       if (s->fields[firstName] != NULL)
  686.          index += StrLen(s->fields[firstName]) + 1;
  687.       companyFieldOffset = index;
  688.       }
  689.    DmWrite(recordP, (ULong)(&d->companyFieldOffset), &companyFieldOffset, sizeof(companyFieldOffset));
  690. }
  691.  
  692.  
  693. /************************************************************
  694.  *
  695.  *  FUNCTION: AddrUnpack
  696.  *
  697.  *  DESCRIPTION: Fills in the AddrDBRecord structure
  698.  *
  699.  *  PARAMETERS: address record to unpack
  700.  *                the address record to unpack into
  701.  *
  702.  *  RETURNS: the record unpacked
  703.  *
  704.  *  CREATED: 1/14/95 
  705.  *
  706.  *  BY: Roger Flores
  707.  *
  708.  *************************************************************/
  709. static void AddrUnpack(AddrPackedDBRecord *src, AddrDBRecordPtr dest)
  710. {
  711.    Int   index;
  712.    ULong flags;
  713.    char *p;
  714.  
  715.    
  716.    dest->options = src->options;
  717.    flags = src->flags.allBits;
  718.    p = &src->firstField;
  719.  
  720.          
  721.    for (index = firstAddressField; index < addressFieldsCount; index++)
  722.       {
  723.       // If the flag is set point to the string else NULL
  724.       if (GetBitMacro(flags, index) != 0)
  725.          {
  726.          dest->fields[index] = p;
  727.          p += StrLen(p) + 1;
  728.          }
  729.       else
  730.          dest->fields[index] = NULL;
  731.       }
  732. }
  733.  
  734.  
  735. /************************************************************
  736.  *
  737.  *  FUNCTION: AddrFindSortPosition
  738.  *
  739.  *  DESCRIPTION: Return where a record is or should be
  740.  *      Useful to find or find where to insert a record.
  741.  *
  742.  *  PARAMETERS: address record
  743.  *
  744.  *  RETURNS: the size in bytes
  745.  *
  746.  *  CREATED: 1/11/95 
  747.  *
  748.  *  BY: Roger Flores
  749.  *
  750.  *************************************************************/
  751. static UInt AddrFindSortPosition(DmOpenRef dbP, AddrPackedDBRecord *newRecord)
  752. {
  753.    Int sortByCompany;
  754.    AddrAppInfoPtr appInfoPtr;
  755.  
  756.    
  757.    appInfoPtr = (AddrAppInfoPtr) AddrAppInfoGetPtr(dbP);
  758.    sortByCompany = appInfoPtr->misc.sortByCompany;
  759.    MemPtrUnlock(appInfoPtr);
  760.          
  761.    return DmFindSortPosition(dbP, (VoidPtr) newRecord, NULL, (DmComparF *) 
  762.       &AddrComparePackedRecords, (Int) sortByCompany);
  763. }
  764.  
  765.  
  766.  
  767. /************************************************************
  768.  *
  769.  *  FUNCTION: StrCmpMatches
  770.  *
  771.  *  DESCRIPTION: Compares two strings and reports the number
  772.  *  of matching characters from the start of the strings.
  773.  *
  774.  *  PARAMETERS: 2 string pointers
  775.  *
  776.  *  RETURNS: number of matching characters between the two strings
  777.  *
  778.  *  CREATED: 6/15/95 
  779.  *
  780.  *  BY: Roger Flores
  781.  *
  782.  *************************************************************/
  783. static Int StrCmpMatches(CharPtr s1, CharPtr s2)
  784. {
  785.    UInt16 matches;
  786.  
  787.    ErrFatalDisplayIf ( s1 == NULL, "Error NULL string parameter"); 
  788.    ErrFatalDisplayIf ( s2 == NULL, "Error NULL string parameter");
  789.  
  790.     TxtCaselessCompare(s1, StrLen(s1), &matches, s2, StrLen(s2), NULL);
  791.     return (matches);
  792. }
  793.  
  794.  
  795. /************************************************************
  796.  *
  797.  *  FUNCTION: AddrNewRecord
  798.  *
  799.  *  DESCRIPTION: Create a new packed record in sorted position
  800.  *
  801.  *  PARAMETERS: database pointer - open db pointer
  802.  *            address record   - pointer to a record to copy into the DB
  803.  *            record index      - to be set to the new record's index
  804.  *
  805.  *  RETURNS: ##0 if successful, errorcode if not
  806.  *             index set if a new record is created.
  807.  *
  808.  *  CREATED: 1/10/95 
  809.  *
  810.  *  BY: Roger Flores
  811.  *
  812.  *************************************************************/
  813. Err AddrNewRecord(DmOpenRef dbP, AddrDBRecordPtr r, UInt *index)
  814. {
  815.    Handle               recordH;
  816.    Err                   err;
  817.    AddrPackedDBRecord*   recordP;
  818.    UInt                   newIndex;
  819.    
  820.  
  821.    // 1) and 2) (make a new chunk with the correct size)
  822.    recordH = (Handle)DmNewHandle(dbP, (ULong) AddrUnpackedSize(r));
  823.    if (recordH == NULL)
  824.       return dmErrMemError;
  825.  
  826.       
  827.    // 3) Copy the data from the unpacked record to the packed one.
  828.    recordP = MemHandleLock(recordH);
  829.    AddrPack(r, recordP);
  830.  
  831.    // Get the index
  832.    newIndex = AddrFindSortPosition(dbP, recordP);
  833.    MemPtrUnlock(recordP);
  834.  
  835.  
  836.    // 4) attach in place
  837.    err = DmAttachRecord(dbP, &newIndex, recordH, 0);
  838.    if (err) 
  839.       MemHandleFree(recordH);
  840.    else
  841.       *index = newIndex;
  842.       
  843.    return err;
  844. }
  845.  
  846.  
  847. /************************************************************
  848.  *
  849.  *  FUNCTION: AddrChangeRecord
  850.  *
  851.  *  DESCRIPTION: Change a record in the Address Database
  852.  *
  853.  *  PARAMETERS: dbP - open database pointer
  854.  *            database index
  855.  *            address record
  856.  *            changed fields
  857.  *
  858.  *  RETURNS: ##0 if successful, errorcode if not
  859.  *
  860.  *  CREATED: 1/14/95 
  861.  *
  862.  *  BY: Roger Flores
  863.  *
  864.  *   COMMENTS:   Records are not stored with extra padding - they
  865.  *   are always resized to their exact storage space.  This avoids
  866.  *   a database compression issue.  The code works as follows:
  867.  *   
  868.  *   1)   get the size of the new record
  869.  *   2)   make the new record
  870.  *   3)   pack the packed record plus the changes into the new record
  871.  *   4)   if the sort position is changes move to the new position
  872.  *   5)   attach in position
  873.  *
  874.  * The handle to the record passed doesn't need to be unlocked 
  875.  * since that chunk is freed by this routine.  It should be discarded.
  876.  *
  877.  *************************************************************/
  878. Err AddrChangeRecord(DmOpenRef dbP, UInt *index, AddrDBRecordPtr r, 
  879.    AddrDBRecordFlags changedFields)
  880. {
  881.    AddrDBRecordType    src;
  882.    Handle             srcH;
  883.    Err                result;
  884.    Handle             recordH=0;
  885.    Handle             oldH;
  886.    Int                i;
  887.    ULong             changes = changedFields.allBits;
  888.    Int                sortByCompany;
  889.    AddrAppInfoPtr    appInfoPtr;
  890.    Boolean            dontMove;
  891.    UInt                attributes;      // to contain the deleted flag
  892.    
  893.    AddrPackedDBRecord*   cmpP;
  894.    AddrPackedDBRecord*   recordP;
  895.  
  896.    
  897.    // We do not assume that r is completely valid so we get a valid
  898.    // AddrDBRecordPtr...
  899.    if ((result = AddrGetRecord(dbP, *index, &src, &srcH)) != 0)
  900.       return result;
  901.    
  902.    // and we apply the changes to it.
  903.    src.options = r->options;         // copy the phone info
  904.    for (i = firstAddressField; i < addressFieldsCount; i++) 
  905.       {
  906.       // If the flag is set point to the string else NULL
  907.       if (GetBitMacro(changes, i) != 0)
  908.          {
  909.          src.fields[i] = r->fields[i];
  910.          RemoveBitMacro(changes, i);
  911.          }
  912.       if (changes == 0)
  913.          break;      // no more changes
  914.       }
  915.  
  916.  
  917.    // 1) and 2) (make a new chunk with the correct size)
  918.    recordH = (Handle)DmNewHandle(dbP, (ULong) AddrUnpackedSize(&src));
  919.    if (recordH == NULL)
  920.       {
  921.       MemHandleUnlock(srcH);      // undo lock from AddrGetRecord above
  922.       return dmErrMemError;
  923.       }
  924.    recordP = MemHandleLock(recordH);
  925.  
  926.       
  927.    // 3) Copy the data from the unpacked record to the packed one.
  928.    AddrPack(&src, recordP);
  929.  
  930.    // The original record is copied and no longer needed.
  931.    MemHandleUnlock(srcH);
  932.    
  933.  
  934.    // 4) if the sort position changes...
  935.    // Check if any of the key fields have changed
  936.    if ((changedFields.allBits & sortKeyFieldBits) == 0) 
  937.       goto attachRecord;
  938.    
  939.    
  940.    // Make sure *index-1 < *index < *index+1, if so it's in sorted 
  941.    // order.  Leave it there.   
  942.    appInfoPtr = (AddrAppInfoPtr) AddrAppInfoGetPtr(dbP);
  943.    sortByCompany = appInfoPtr->misc.sortByCompany;
  944.    MemPtrUnlock(appInfoPtr);
  945.  
  946.    if (*index > 0)
  947.       {
  948.       // This record wasn't deleted and deleted records are at the end of the
  949.       // database so the prior record may not be deleted!
  950.       cmpP = MemHandleLock(DmQueryRecord(dbP, *index-1));
  951.       dontMove = (AddrComparePackedRecords (cmpP,  recordP, sortByCompany,
  952.                      NULL, NULL, 0) == -1);
  953.       MemPtrUnlock(cmpP);
  954.       }
  955.    else 
  956.       dontMove = true;
  957.  
  958.  
  959.    if (*index+1 < DmNumRecords (dbP))
  960.       {
  961.       DmRecordInfo(dbP, *index+1, &attributes, NULL, NULL);
  962.       if (attributes & dmRecAttrDelete)
  963.          ;      // don't move it after the deleted record!
  964.       else {
  965.          cmpP = MemHandleLock(DmQueryRecord(dbP, *index+1));
  966.          dontMove = dontMove && (AddrComparePackedRecords (recordP, cmpP, 
  967.                      sortByCompany, NULL, NULL, 0) == -1);
  968.          MemPtrUnlock(cmpP);
  969.          }
  970.       }
  971.  
  972.       
  973.    if (dontMove) 
  974.       goto attachRecord;
  975.  
  976.    
  977.    
  978.    // The record isn't in the right position.  Move it.
  979.    i = AddrFindSortPosition(dbP, recordP);
  980.    DmMoveRecord(dbP, *index, i);
  981.    if (i > *index) i--;
  982.    *index = i;                  // return new position
  983.  
  984.  
  985.    // Attach the new record to the old index,  the preserves the 
  986.    // category and record id.
  987. attachRecord:
  988.  
  989.    result = DmAttachRecord(dbP, index, recordH, &oldH);
  990.    MemPtrUnlock(recordP);
  991.    if (result) return result;
  992.  
  993.    MemHandleFree(oldH);
  994.    return 0;
  995. }
  996.  
  997.  
  998.  
  999. /************************************************************
  1000.  *
  1001.  *  FUNCTION: AddrGetRecord
  1002.  *
  1003.  *  DESCRIPTION: Get a record from the Address Database
  1004.  *
  1005.  *  PARAMETERS: database pointer - open db pointer
  1006.  *            database index - index of record to lock
  1007.  *            address record pointer - pointer address structure
  1008.  *            address record - handle to unlock when done
  1009.  *
  1010.  *  RETURNS: ##0 if successful, errorcode if not
  1011.  *    The record's handle is locked so that the pointer to 
  1012.  *  strings within the record remain pointing to valid chunk
  1013.  *  versus the record randomly moving.  Unlock the handle when
  1014.  *  AddrDBRecord is destroyed.
  1015.  *
  1016.  *  CREATED: 1/14/95 
  1017.  *
  1018.  *  BY: Roger Flores
  1019.  *
  1020.  *************************************************************/
  1021. Err AddrGetRecord(DmOpenRef dbP, UInt index, AddrDBRecordPtr recordP, 
  1022.    Handle *recordH)
  1023. {
  1024.    AddrPackedDBRecord *src;
  1025.  
  1026.    *recordH = DmQueryRecord(dbP, index);
  1027.    src = (AddrPackedDBRecord *) MemHandleLock(*recordH);
  1028.    if (src == NULL)
  1029.       return dmErrIndexOutOfRange;
  1030.    
  1031.    AddrUnpack(src, recordP);
  1032.    
  1033.    return 0;
  1034. }
  1035.  
  1036.  
  1037. /***********************************************************************
  1038.  *
  1039.  * FUNCTION:    RecordContainsData
  1040.  *
  1041.  * DESCRIPTION: Checks the record returns true if it contains any data.
  1042.  *
  1043.  * PARAMETERS:  recordP  - a pointer to an address record
  1044.  *
  1045.  * RETURNED:    true if one of the fields has data
  1046.  *
  1047.  * REVISION HISTORY:
  1048.  *         Name   Date      Description
  1049.  *         ----   ----      -----------
  1050.  *         rsf   12/3/97   Initial Revision
  1051.  *
  1052.  ***********************************************************************/
  1053. Boolean RecordContainsData (AddrDBRecordPtr recordP)
  1054. {
  1055.    UInt i;
  1056.    
  1057.    
  1058.     // Look for a field which isn't empty
  1059.     for (i = firstAddressField; i < addressFieldsCount; i++)
  1060.         {
  1061.         if (recordP->fields[i] != NULL)
  1062.            return true;
  1063.         }
  1064.     
  1065.     return false;
  1066. }
  1067.  
  1068.  
  1069. /***********************************************************************
  1070.  *
  1071.  * FUNCTION:    RecordContainsField
  1072.  *
  1073.  * DESCRIPTION: Check if a packed record contains a desired field.           
  1074.  *
  1075.  * PARAMETERS:  recordP  - pointer to the record to search
  1076.  *              field - type of field to find.
  1077.  *
  1078.  * RETURNED:    true if the record contains the field.
  1079.  *
  1080.  * REVISION HISTORY:
  1081.  *         Name   Date      Description
  1082.  *         ----   ----      -----------
  1083.  *         Roger   7/9/96   Initial Revision
  1084.  *
  1085.  ***********************************************************************/
  1086. static Boolean RecordContainsField(AddrPackedDBRecord *packedRecordP, 
  1087.    AddressLookupFields field, UIntPtr phoneP, Int direction,
  1088.    AddressFields lookupFieldMap[])
  1089. {
  1090.    int index;
  1091.    int stopIndex;
  1092.    int phoneType;
  1093.    
  1094.    
  1095.    switch (field)
  1096.        {
  1097.       case addrLookupSortField:
  1098.          return packedRecordP->flags.allBits & sortKeyFieldBits;
  1099.       
  1100.       case addrLookupListPhone:
  1101.          return GetBitMacro(packedRecordP->flags.allBits, firstPhoneField + 
  1102.             packedRecordP->options.phones.displayPhoneForList);
  1103.       
  1104.       case addrLookupNoField:
  1105.          return true;
  1106.       
  1107.       default:
  1108.             if (!IsPhoneLookupField(field))
  1109.                 return GetBitMacro(packedRecordP->flags.allBits, lookupFieldMap[field]) != 0;
  1110.          
  1111.             phoneType = field - addrLookupWork;
  1112.             index = firstPhoneField + *phoneP;
  1113.             if (direction == dmSeekForward)
  1114.                 stopIndex = lastPhoneField + direction;
  1115.             else
  1116.                 stopIndex = firstPhoneField + direction;
  1117.             
  1118.          while (index != stopIndex)
  1119.              {
  1120.             // If the phone field is the type requested and it's not empty 
  1121.             // return it.
  1122.             if (GetPhoneLabel(packedRecordP, index) == phoneType &&
  1123.                 GetBitMacro(packedRecordP->flags.allBits, index))
  1124.                 {
  1125.                *phoneP = index - firstPhoneField;
  1126.                return true;
  1127.                  }
  1128.               index += direction;
  1129.              }
  1130.           
  1131.          // The phone type wasn't used.
  1132.          if (direction == dmSeekForward)
  1133.              *phoneP = 0;                              // Reset for the next record
  1134.          else
  1135.             *phoneP = numPhoneFields - 1;      // Reset for the next record
  1136.           
  1137.          return false;
  1138.         }
  1139. }
  1140.  
  1141.  
  1142. /************************************************************
  1143.  *
  1144.  *  FUNCTION: AddrChangeSortOrder
  1145.  *
  1146.  *  DESCRIPTION: Change the Address Database's sort order
  1147.  *
  1148.  *  PARAMETERS: dbP - open database pointer
  1149.  *            TRUE if sort by company
  1150.  *
  1151.  *  RETURNS: nothing
  1152.  *
  1153.  *  CREATED: 1/17/95 
  1154.  *
  1155.  *  BY: Roger Flores
  1156.  *
  1157.  *************************************************************/
  1158. Err AddrChangeSortOrder(DmOpenRef dbP, Boolean sortByCompany)
  1159. {
  1160.    AddrAppInfoPtr appInfoPtr;
  1161.    AddrAppInfoPtr   nilP=0;
  1162.    AddrDBMisc      misc;
  1163.  
  1164.    
  1165.    appInfoPtr = (AddrAppInfoPtr) AddrAppInfoGetPtr(dbP);
  1166.    misc = appInfoPtr->misc;
  1167.    misc.sortByCompany = sortByCompany;
  1168.    DmWrite(appInfoPtr, (ULong) &nilP->misc, &misc, sizeof(misc));
  1169.    MemPtrUnlock(appInfoPtr);
  1170.    
  1171.    DmQuickSort(dbP, (DmComparF *) &AddrComparePackedRecords, (Int) sortByCompany);
  1172.    return 0;
  1173. }
  1174.  
  1175.  
  1176. /***********************************************************************
  1177.  *
  1178.  * FUNCTION:    AddrLookupSeekRecord
  1179.  *
  1180.  * DESCRIPTION: Given the index of a record, scan 
  1181.  *              forewards or backwards for displayable records.           
  1182.  *
  1183.  * PARAMETERS:  indexP  - pointer to the index of a record to start from;
  1184.  *                        the index of the record sought is returned in
  1185.  *                        this parameter.
  1186.  *
  1187.  *              offset  - number of records to skip:   
  1188.  *                           0 - mean seek from the current record to the
  1189.  *                             next display record, if the current record is
  1190.  *                             a display record, its index is retuned.
  1191.  *                         1 - mean seek foreward, skipping one displayable 
  1192.  *                             record
  1193.  *                        -1 - means seek backwards, skipping one 
  1194.  *                             displayable record
  1195.  *                             
  1196.  *
  1197.  * RETURNED:    true if a displayable record was found.
  1198.  *
  1199.  * REVISION HISTORY:
  1200.  *         Name   Date      Description
  1201.  *         ----   ----      -----------
  1202.  *         Roger   7/9/96   Initial Revision
  1203.  *
  1204.  ***********************************************************************/
  1205. extern Boolean AddrLookupSeekRecord (DmOpenRef dbP, UIntPtr indexP, 
  1206.    UIntPtr phoneP, Int offset, Int direction, 
  1207.    AddressLookupFields field1, AddressLookupFields field2, 
  1208.    AddressFields lookupFieldMap[])
  1209. {
  1210.    UInt index;
  1211.    UInt oldIndex;
  1212.    UInt count;
  1213.    UInt numRecords;
  1214.    Handle recordH;
  1215.    Boolean match;
  1216.    UInt phone;
  1217.    Boolean searchPhones;
  1218.    AddrPackedDBRecord *packedRecordP;
  1219.    
  1220.  
  1221.    ErrFatalDisplayIf ( (direction != dmSeekForward) && (direction != dmSeekBackward), 
  1222.       "Bad Param");
  1223.  
  1224.    ErrFatalDisplayIf ( (offset < 0), "Bad param"); 
  1225.    
  1226.  
  1227.    index = *indexP;
  1228.    phone = *phoneP;
  1229.    
  1230.    searchPhones = IsPhoneLookupField(field1) || IsPhoneLookupField(field2);
  1231.  
  1232.    numRecords = DmNumRecords(dbP);
  1233.    
  1234.    if (index >= numRecords)
  1235.        {
  1236.       if (direction == dmSeekForward)
  1237.          return false;
  1238.       else
  1239.          index = numRecords - 1;
  1240.       }
  1241.    
  1242.    
  1243.    // Moving forward?
  1244.    if (direction == dmSeekForward )
  1245.       count = numRecords - index;
  1246.    else
  1247.       count = index + 1;
  1248.       
  1249.    // Loop through the records
  1250.    while (count--) {
  1251.    
  1252.       // Make sure the current record isn't hidden.  If so skip it and find the
  1253.       // next non hidden record.  Decrease the record count to search by the number
  1254.       // of records skipped.
  1255.       oldIndex = index;
  1256.       if (DmSeekRecordInCategory (dbP, &index, 0, direction, dmAllCategories))
  1257.          {
  1258.          // There are no more records.
  1259.          break;
  1260.          }
  1261.       if (index != oldIndex)
  1262.          {
  1263.          if (direction == dmSeekForward)
  1264.             count -= index - oldIndex;
  1265.          else
  1266.             count -= oldIndex - index;
  1267.          }
  1268.       
  1269.       recordH = DmQueryRecord(dbP, index);
  1270.       
  1271.       // If we have found a deleted record stop the search.
  1272.       if (!recordH)
  1273.          break;
  1274.       
  1275.       packedRecordP = MemHandleLock(recordH);
  1276.       if (!packedRecordP)
  1277.          goto Exit;
  1278.  
  1279.       match = RecordContainsField(packedRecordP, field1, &phone, direction, lookupFieldMap) &&
  1280.          RecordContainsField(packedRecordP, field2, &phone, direction, lookupFieldMap);
  1281.       
  1282.       MemHandleUnlock(recordH);
  1283.       
  1284.       if (match) 
  1285.          {
  1286.          *indexP = index;
  1287.          *phoneP = phone;
  1288.          if (offset == 0) return true;
  1289.          offset--;
  1290.          }
  1291.       
  1292.       // Look for another phone in this record if one was found or
  1293.       // else look at the next record.
  1294.       if (searchPhones && match)
  1295.          {
  1296.          phone += direction;
  1297.          // We their are no more phones to search so advance to next record
  1298.          if (phone == 0xffff || numPhoneFields <= phone)
  1299.             {
  1300.             if (direction == dmSeekForward)
  1301.                phone = 0;
  1302.             else
  1303.                phone = numPhoneFields - 1;
  1304.  
  1305.             index += direction;
  1306.             }
  1307.          else
  1308.             {
  1309.             // Since we are going to search this record again bump the count up
  1310.             // by one.  This loop is supposed to loop once per record to search.
  1311.             count++;
  1312.             }
  1313.          }
  1314.       else
  1315.          index += direction;
  1316.          
  1317.       }
  1318.    
  1319.    return false;
  1320.  
  1321. Exit:
  1322.    ErrDisplay("Err seeking rec");
  1323.  
  1324.    return false;
  1325. }
  1326.  
  1327.  
  1328. /************************************************************
  1329.  *
  1330.  *  FUNCTION: AddrLookupString
  1331.  *
  1332.  *  DESCRIPTION: Return which record contains the most of
  1333.  *      the string passed.  If no string is passed or there
  1334.  *  aren't any records then false is returned.
  1335.  *
  1336.  *  PARAMETERS: address record
  1337.  *                key - string to lookup record with
  1338.  *                sortByCompany - how the db is sorted
  1339.  *                category -  the category to search in
  1340.  *                recordP - to contain the record found
  1341.  *                completeMatch -  true if a record contains all 
  1342.  *                                 of the key
  1343.  *
  1344.  *  RETURNS: the record in recordP or false
  1345.  *             completeMatch -  true if a record contains all 
  1346.  *                              of the key
  1347.  *
  1348.  *  CREATED: 6/15/95 
  1349.  *
  1350.  *  BY: Roger Flores
  1351.  *
  1352.  *************************************************************/
  1353. Boolean AddrLookupString(DmOpenRef dbP, CharPtr key, 
  1354.    Boolean sortByCompany, UInt category, UIntPtr recordP, Boolean *completeMatch)
  1355. {
  1356.    Int                   numOfRecords;
  1357.    Handle                rH;
  1358.    AddrPackedDBRecord*   r;
  1359.    UInt                  kmin, probe, probe2, i;      // all positions in the database.
  1360.    Int                   result;                     // result of comparing two records
  1361.    UInt                   whichKey;
  1362.    char*                  recordKey;
  1363.    UInt                   matches1, matches2;
  1364.  
  1365.    
  1366.    // If there isn't a key to search with stop the with the first record.
  1367.    if (key == NULL || *key == '\0')
  1368.       {
  1369.       *completeMatch = true;
  1370.       return false;
  1371.       }
  1372.       
  1373.    numOfRecords = DmNumRecords(dbP);
  1374.    if (numOfRecords == 0)
  1375.       return false;
  1376.    
  1377.    result = 0;
  1378.    kmin = probe = 0;
  1379.    rH = 0;
  1380.    
  1381.    
  1382.    while (numOfRecords > 0)
  1383.       {
  1384.       i = numOfRecords / 2;
  1385.       probe = kmin + i;
  1386.  
  1387.  
  1388.       // Compare the two records.  Treat deleted records as greater.
  1389.       // If the records are equal look at the following position.
  1390.       if (rH) 
  1391.          MemHandleUnlock(rH);
  1392.       rH = DmQueryRecord(dbP, probe);
  1393.       if (rH == 0)
  1394.          {
  1395.          result = -1;      // Delete record is greater
  1396.          }
  1397.       else
  1398.          {
  1399.          r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1400.          ErrFatalDisplayIf(r == 0, "Addr bsearch: data somehow missing");
  1401.             
  1402.             
  1403.          // Compare the string to the first sort key only
  1404.          whichKey = 1;
  1405.          AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1406.       
  1407.          if (recordKey == NULL)
  1408.             result = 1;
  1409.          else
  1410.             result = StrCaselessCompare(key, recordKey);
  1411.  
  1412.  
  1413.          // If equal stop here!  We don't want the position after.
  1414.          if (result == 0)
  1415.             goto findRecordInCategory;
  1416.          }
  1417.  
  1418.  
  1419.       ErrFatalDisplayIf(result == 0, "Impossible bsearch state");
  1420.       
  1421.       // More likely than < 0 because of deleted records
  1422.       if (result < 0)
  1423.          numOfRecords = i;
  1424.       else
  1425.          {
  1426.          kmin = probe + 1;
  1427.          numOfRecords = numOfRecords - i - 1;
  1428.          }
  1429.       }
  1430.  
  1431.    if (result >= 0)
  1432.       probe++;
  1433.       
  1434. findRecordInCategory:
  1435.    if (rH)
  1436.       MemHandleUnlock(rH);
  1437.    
  1438.    // At this point probe is the position where the string could be
  1439.    // inserted.  It is in between two entries.  Neither the record
  1440.    // before or after may have ANY letters in common, especially after
  1441.    // those records in other catergories are skipped.  Go with the
  1442.    // record that has the most letters in common.
  1443.    
  1444.    
  1445.    // Make sure the record returned is of the same category.
  1446.    // If not return the first prior record of the same category.
  1447.    probe2 = probe;
  1448.    if (!DmSeekRecordInCategory (dbP, &probe, 0, dmSeekForward, category))
  1449.       {
  1450.       // Now count the number of matching characters in probe
  1451.       rH = DmQueryRecord(dbP, probe);      // No deleted record possible
  1452.       r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1453.       ErrFatalDisplayIf(r == 0, "Addr bsearch: data somehow missing");
  1454.       whichKey = 1;
  1455.       AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1456.       if (recordKey == NULL)
  1457.          matches1 = 0;
  1458.       else
  1459.          matches1 = StrCmpMatches(key, recordKey);
  1460.       
  1461.       MemHandleUnlock(rH);
  1462.       }
  1463.    else
  1464.       {
  1465.       // No record in this category was found or probe is past all
  1466.       // records in this category.  Either way there aren't any matching
  1467.       // letters.
  1468.       matches1 = 0;
  1469.       }
  1470.    
  1471.  
  1472.  
  1473.    // Sometimes the record before has more matching letters. Check it.
  1474.    // Passing DmSeekRecordInCategory an offset of 1 doesn't work
  1475.    // when probe is at the end of the database and there isn't at least
  1476.    // one record to skip.
  1477.    probe2 = probe - 1;
  1478.    if (probe == 0 ||
  1479.       DmSeekRecordInCategory (dbP, &probe2, 0, dmSeekBackward, category))
  1480.       {
  1481.       if (matches1 > 0)
  1482.          {
  1483.          // Go with probe because they have at least some letters in common.
  1484.          *recordP = probe;   //
  1485.          *completeMatch = (matches1 == StrLen(key));
  1486.          return true;
  1487.          }
  1488.       else
  1489.          {
  1490.          // probe has no letters in common and nothing earlier in this category
  1491.          // was found so this is a failed lookup.
  1492.          *completeMatch = false;
  1493.          return false;
  1494.          }
  1495.       }
  1496.    
  1497.    
  1498.    // Now count the number of matching characters in probe2
  1499.    rH = DmQueryRecord(dbP, probe2);      // No deleted record possible
  1500.    r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1501.    ErrFatalDisplayIf(r == 0, "Addr bsearch: data somehow missing");
  1502.    whichKey = 1;
  1503.    AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1504.    if (recordKey == NULL)
  1505.       matches2 = 0;
  1506.    else
  1507.       matches2 = StrCmpMatches(key, recordKey);
  1508.    MemHandleUnlock(rH);
  1509.  
  1510.  
  1511.    // Now, return the probe which has the most letters in common.
  1512.    if (matches1 > matches2)
  1513.       {
  1514.       *completeMatch = (matches1 == StrLen(key));
  1515.       *recordP = probe;
  1516.       }
  1517.    else
  1518.    if (matches1 == 0 && matches2 == 0)
  1519.       {
  1520.       *completeMatch = false;
  1521.       return false;            // no item with same first letter found
  1522.       }
  1523.    else
  1524.       {
  1525.       // The first item matches as much or more as the second item
  1526.       *recordP = probe2;
  1527.       
  1528.       // If the prior item in the category has the same number of
  1529.       // matching letters use it instead.  Repeat to find the
  1530.       // earliest such match.
  1531.       while (!DmSeekRecordInCategory (dbP, &probe2, 1, dmSeekBackward, category))
  1532.          {
  1533.          rH = DmQueryRecord(dbP, probe2);
  1534.          r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1535.          ErrFatalDisplayIf(r == 0, "Addr bsearch: data somehow missing");
  1536.  
  1537.          // Compare the string to the first sort key only
  1538.          whichKey = 1;
  1539.          AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1540.       
  1541.          if (recordKey == NULL)
  1542.             matches1 = 0;
  1543.          else
  1544.             matches1 = StrCmpMatches(key, recordKey);
  1545.                
  1546.          MemHandleUnlock(rH);
  1547.  
  1548.          if (matches1 == matches2)
  1549.             *recordP = probe2;
  1550.          else
  1551.             break;
  1552.          }
  1553.          
  1554.       *completeMatch = (matches2 == StrLen(key));
  1555.       }      
  1556.  
  1557.    return true;
  1558. }
  1559.  
  1560.  
  1561. /************************************************************
  1562.  *
  1563.  *  FUNCTION:    AddrLookupLookupString
  1564.  *
  1565.  *  DESCRIPTION: Return which record contains the most of
  1566.  *      the string passed.  If no string is passed or there
  1567.  *  aren't any records then false is returned.
  1568.  *
  1569.  *  PARAMETERS: address record
  1570.  *                key - string to lookup record with
  1571.  *                sortByCompany - how the db is sorted
  1572.  *                vars -  Lookup variables
  1573.  *                recordP - to contain the record found
  1574.  *                phoneP - to contain the phone found
  1575.  *                completeMatch -  true if a record contains all 
  1576.  *                                 of the key
  1577.  *
  1578.  *  RETURNS: the record in recordP or false
  1579.  *             completeMatch -  true if a record contains all 
  1580.  *                              of the key
  1581.  *
  1582.  * RETURNED:    false is return if a displayable record was not found.
  1583.  *
  1584.  * REVISION HISTORY:
  1585.  *         Name   Date      Description
  1586.  *         ----   ----      -----------
  1587.  *         Roger   7/19/96   Initial Revision
  1588.  *
  1589.  *************************************************************/
  1590. extern Boolean AddrLookupLookupString(DmOpenRef dbP, CharPtr key, 
  1591.    Boolean sortByCompany, AddressLookupFields field1, 
  1592.    AddressLookupFields field2, UIntPtr recordP, UIntPtr phoneP, 
  1593.    AddressFields lookupFieldMap[], Boolean *completeMatch, 
  1594.    Boolean *uniqueMatch)
  1595. {
  1596.    Int                   numOfRecords;
  1597.    Handle                rH;
  1598.    AddrPackedDBRecord*   r;
  1599. //   UInt                  kmin, i;                     // all positions in the database.
  1600.    UInt                  probe, probe2         ;      // all positions in the database.
  1601.    UInt                  phoneProbe, phoneProbe2;
  1602. //   Int                   result;                     // result of comparing two records
  1603.    UInt                   whichKey;
  1604.    char*                  recordKey;
  1605.    UInt                   matches1, matches2;
  1606.    AddressFields         searchField;
  1607.    AddrDBRecordFlags      searchFieldFlag;
  1608.  
  1609.    
  1610.    *uniqueMatch = false;
  1611.    // If there isn't a key to search with stop the with the first record.
  1612.    if (key == NULL || *key == '\0')
  1613.       {
  1614.       *completeMatch = true;
  1615.       return false;
  1616.       }
  1617.       
  1618.    numOfRecords = DmNumRecords(dbP);
  1619.    if (numOfRecords == 0)
  1620.       return false;
  1621.    
  1622.    // Performing a lookup on the sort field allows the use a binary search which
  1623.    // takes advantage of the ordered field.
  1624.    if (field1 == addrLookupSortField)
  1625.       {
  1626.       // Perform the standard lookup on the sort fields looking at all categories.
  1627.       if (!AddrLookupString(dbP, key, sortByCompany, dmAllCategories, 
  1628.          recordP, completeMatch))
  1629.          return false;   // nothing matched
  1630.       
  1631.       
  1632.       // At this point probe is the position where the string could be
  1633.       // inserted.  It is in between two entries.  Neither the record
  1634.       // before or after may have ANY letters in common, especially after
  1635.       // those records in other catergories are skipped.  Go with the
  1636.       // record that has the most letters in common.
  1637.       
  1638.       
  1639.       // Make sure the record returned is of the same category.
  1640.       // If not return the first prior record of the same category.
  1641.       probe2 = probe = *recordP;
  1642.       phoneProbe2 = phoneProbe = 0;
  1643.       if (AddrLookupSeekRecord (dbP, &probe, &phoneProbe, 0, dmSeekForward, 
  1644.          field1, field2, lookupFieldMap))
  1645.          {
  1646.          // Now count the number of matching characters in probe
  1647.          rH = DmQueryRecord(dbP, probe);      // No deleted record possible
  1648.          r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1649.          ErrFatalDisplayIf(r == 0, "AddrLookup bsearch: data somehow missing");
  1650.          whichKey = 1;
  1651.          AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1652.          if (recordKey == NULL)
  1653.             matches1 = 0;
  1654.          else
  1655.             matches1 = StrCmpMatches(key, recordKey);
  1656.          
  1657.          MemHandleUnlock(rH);
  1658.          }
  1659.       else
  1660.          {
  1661.          // No record in this category was found or probe is past all
  1662.          // records in this category.  Either way there aren't any matching
  1663.          // letters.
  1664.          matches1 = 0;
  1665.          }
  1666.       
  1667.  
  1668.       *uniqueMatch = true;
  1669.  
  1670.  
  1671.       // Sometimes the record before has more matching letters. Check it.
  1672.       // Passing DmSeekRecordInCategory an offset of 1 doesn't work
  1673.       // when probe is at the end of the database and there isn't at least
  1674.       // one record to skip.
  1675.       probe2 = probe - 1;
  1676.       if (probe == 0 ||
  1677.          !AddrLookupSeekRecord (dbP, &probe2, &phoneProbe2, 0, dmSeekBackward, 
  1678.             field1, field2, lookupFieldMap))
  1679.          {
  1680.          // There isn't an earlier record.  Try to find a following record.
  1681.          probe2 = probe + 1;
  1682.          phoneProbe2 = phoneProbe;
  1683.          if (!AddrLookupSeekRecord (dbP, &probe2, &phoneProbe2, 0, dmSeekForward, 
  1684.             field1, field2, lookupFieldMap))
  1685.             {
  1686.             // There isn't a following record.  Try to use the probe.
  1687.             if (matches1 > 0)
  1688.                {
  1689.                // Go with probe because they have at least some letters in common.
  1690.                *recordP = probe;   //
  1691.                *phoneP = phoneProbe;
  1692.                *completeMatch = (matches1 == StrLen(key));
  1693.                return true;
  1694.                }
  1695.             else
  1696.                {
  1697.                // probe has no letters in common and nothing earlier in this category
  1698.                // was found so this is a failed lookup.
  1699.                *completeMatch = false;
  1700.                return false;
  1701.                }
  1702.             }
  1703.          }
  1704.       
  1705.       
  1706.       // Now count the number of matching characters in probe2
  1707.       rH = DmQueryRecord(dbP, probe2);      // No deleted record possible
  1708.       r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1709.       ErrFatalDisplayIf(r == 0, "AddrLookup bsearch: data somehow missing");
  1710.       whichKey = 1;
  1711.       AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1712.       if (recordKey == NULL)
  1713.          matches2 = 0;
  1714.       else
  1715.          matches2 = StrCmpMatches(key, recordKey);
  1716.       MemHandleUnlock(rH);
  1717.  
  1718.  
  1719.       // Now, return the probe which has the most letters in common.
  1720.       if (matches1 > matches2)
  1721.          {
  1722.          *completeMatch = (matches1 == StrLen(key));
  1723.          *recordP = probe;
  1724.          *phoneP = phoneProbe;
  1725.          
  1726.          // If the next item has the same number of
  1727.          // matching letters then the match is not unique.
  1728.          probe2 = probe;
  1729.          phoneProbe2 = phoneProbe;
  1730.          if (AddrLookupSeekRecord (dbP, &probe2, &phoneProbe2, 1, dmSeekForward, 
  1731.             field1, field2, lookupFieldMap))
  1732.             {
  1733.             rH = DmQueryRecord(dbP, probe2);
  1734.             r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1735.             ErrFatalDisplayIf(r == 0, "AddrLookup bsearch: data somehow missing");
  1736.  
  1737.             // Compare the string to the first sort key only
  1738.             whichKey = 1;
  1739.             AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1740.          
  1741.             if (recordKey == NULL)
  1742.                matches2 = 0;
  1743.             else
  1744.                matches2 = StrCmpMatches(key, recordKey);
  1745.                   
  1746.             MemHandleUnlock(rH);
  1747.  
  1748.             if (matches1 <= matches2)
  1749.                {
  1750.                *uniqueMatch = false;
  1751.                }
  1752.             }
  1753.          }
  1754.       else
  1755.       if (matches1 == 0 && matches2 == 0)
  1756.          {
  1757.          *completeMatch = false;
  1758.          *uniqueMatch = false;
  1759.          return false;            // no item with same first letter found
  1760.          }
  1761.       else
  1762.          {
  1763.          // The first item matches as much or more as the second item
  1764.          *recordP = probe2;
  1765.          *phoneP = phoneProbe2;
  1766.          
  1767.          // If the prior item in the category has the same number of
  1768.          // matching letters use it instead.  Repeat to find the
  1769.          // earliest such match.
  1770.          while (AddrLookupSeekRecord (dbP, &probe2, &phoneProbe2, 1, dmSeekBackward, 
  1771.             field1, field2, lookupFieldMap))
  1772.             {
  1773.             rH = DmQueryRecord(dbP, probe2);
  1774.             r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1775.             ErrFatalDisplayIf(r == 0, "AddrLookup bsearch: data somehow missing");
  1776.  
  1777.             // Compare the string to the first sort key only
  1778.             whichKey = 1;
  1779.             AddrFindKey(r, &recordKey, &whichKey, sortByCompany);
  1780.          
  1781.             if (recordKey == NULL)
  1782.                matches1 = 0;
  1783.             else
  1784.                matches1 = StrCmpMatches(key, recordKey);
  1785.                   
  1786.             MemHandleUnlock(rH);
  1787.  
  1788.             if (matches1 == matches2)
  1789.                {
  1790.                *recordP = probe2;
  1791.                *phoneP = phoneProbe2;
  1792.                }
  1793.             else
  1794.                break;
  1795.             }
  1796.             
  1797.          *completeMatch = (matches2 == StrLen(key));
  1798.          *uniqueMatch = false;
  1799.          }      
  1800.  
  1801.       return true;
  1802.          
  1803.       }
  1804.    else
  1805.       {
  1806.       // Peform a lookup based on unordered data.  This gets real slow with lots of data
  1807.       // Because to check for uniqueness we must search every record.  This means on average
  1808.       // this lookup is twice as slow as it would be it it could stop with the first match.
  1809.       AddrDBRecordType record;
  1810.       
  1811.       
  1812.       *completeMatch = false;
  1813.       
  1814.       matches1 = 0;         // treat this as the most matches
  1815.  
  1816.       // cache these values      
  1817.       searchField = lookupFieldMap[field1];
  1818.       searchFieldFlag.allBits = BitAtPosition(field1);
  1819.       
  1820.       // Start with the first record and look at each record until there are no more.
  1821.       // Look for the record with the most number of matching records.  Even if we found
  1822.       // a record containing all the record we are searching for we must still look
  1823.       // for one more complete match to confirm or deny uniqueness of the match.
  1824.       probe2 = 0;
  1825.       phoneProbe2 = 0;
  1826.       while (AddrLookupSeekRecord (dbP, &probe2, &phoneProbe2, 1, dmSeekForward, 
  1827.          field1, field2, lookupFieldMap))
  1828.          {
  1829.          rH = DmQueryRecord(dbP, probe2);
  1830.          r = (AddrPackedDBRecord *) MemHandleLock(rH);
  1831.          ErrFatalDisplayIf(r == 0, "AddrLookup bsearch: data somehow missing");
  1832.  
  1833.          // Compare the string to the search field
  1834.          if (r->flags.allBits & searchFieldFlag.allBits)
  1835.             {
  1836.             AddrUnpack(r, &record);
  1837.             recordKey = record.fields[searchField];
  1838.          
  1839.             if (recordKey == NULL)
  1840.                matches2 = 0;
  1841.             else
  1842.                matches2 = StrCmpMatches(key, recordKey);
  1843.             }
  1844.          else
  1845.             {
  1846.             matches2 = 0;
  1847.             }
  1848.                
  1849.          MemHandleUnlock(rH);
  1850.          
  1851.          if (matches2 > matches1)
  1852.             {
  1853.             matches1 = matches2;      // the most matches so far
  1854.             
  1855.             *recordP = probe2;      // return the best record
  1856.             *phoneP = phoneProbe2;
  1857.             
  1858.             *completeMatch = (matches2 == StrLen(key));
  1859.             }
  1860.          // Did we find another record which is a complete match?
  1861.          else if (matches2 > 0 &&
  1862.             matches1 == matches2 &&
  1863.             *completeMatch)
  1864.             {
  1865.             *uniqueMatch = false;
  1866.             return true;
  1867.             }
  1868.          else
  1869.             {
  1870.             // The record is a matching failure.  Since AddrLookupSeekRecord is going
  1871.             // to return this record again for every phone field we cheat by specifying
  1872.             // the last phone field to skip all other entries.
  1873. //            phoneProbe2 = numPhoneFields - 1;
  1874.             }
  1875.          }
  1876.       
  1877.       
  1878.       // Was at least one record found with at least one matching character?
  1879.       if (matches1 > 0)
  1880.          {
  1881.          // At this point every record was searched and no other match was found.
  1882.          *uniqueMatch = true;
  1883.          
  1884.          return true;
  1885.          }
  1886.       }
  1887.    
  1888.    return false;
  1889. }
  1890.  
  1891.